package cn.jtfadmin.base.shiro.support;

import cn.jtfadmin.base.lang.common.security.UserDetails;
import cn.jtfadmin.base.lang.constant.SecurityConstant;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.RememberMeAuthenticationToken;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.CacheManagerAware;
import org.apache.shiro.mgt.RememberMeManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.subject.WebSubject;
import org.apache.shiro.web.subject.WebSubjectContext;
import org.apache.shiro.web.util.RequestPairSource;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Objects;
import java.util.Set;

/**
 * 缓存类型的记住我管理器<br/>
 *
 * @author jtf
 * @since 0.0.1
 */
public class CacheRememberMeManager implements RememberMeManager, CacheManagerAware {

    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
     * 缓存管理器
     */
    private CacheManager cacheManager;

    /**
     * 缓存名
     */
    private String cacheName = "shiro-rememberMe-cache";

    /**
     * 请求头中的认证头
     */
    public static final String DEFAULT_TOKEN_REQUEST_HEADER_NAME = SecurityConstant.AUTH_HEADER_NAME;

    /**
     * 请求头中的认证头
     */
    private String tokenRequestHeaderName = DEFAULT_TOKEN_REQUEST_HEADER_NAME;

    /**
     * 自定义token请求头名称
     * @param tokenRequestHeaderName
     */
    public void setTokenRequestHeaderName(String tokenRequestHeaderName) {
        this.tokenRequestHeaderName = tokenRequestHeaderName;
    }

    /**
     * 当前人用户基本信息
     */
    private static class UserInfo implements Serializable{
        /**
         * 用户id
         */
        private String userId;
        /**
         * 用户类型
         */
        private String type;

        /**
         * realmName
         */
        private String realmName;

        public String getUserId() {
            return userId;
        }

        public void setUserId(String userId) {
            this.userId = userId;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public String getRealmName() {
            return realmName;
        }

        public void setRealmName(String realmName) {
            this.realmName = realmName;
        }

        @Override
        public String toString() {
            return "UserInfo{" +
                    "userId='" + userId + '\'' +
                    ", type='" + type + '\'' +
                    ", realmName='" + realmName + '\'' +
                    '}';
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            UserInfo userInfo = (UserInfo) o;
            return Objects.equals(userId, userInfo.userId) &&
                    Objects.equals(type, userInfo.type);
        }

        @Override
        public int hashCode() {
            return Objects.hash(userId, type);
        }
    }

    @Override
    public PrincipalCollection getRememberedPrincipals(SubjectContext subjectContext) {
        PrincipalCollection principalCollection = null;
        Serializable sessionId = subjectContext.getSessionId();
        if(Objects.nonNull(sessionId)){
            principalCollection = getPrincipals(sessionId);
        }
        if(Objects.nonNull(principalCollection)){
            return principalCollection;
        }
        if (subjectContext instanceof WebSubjectContext) {
            WebSubjectContext webSubjectContext = (WebSubjectContext) subjectContext;
            principalCollection = getPrincipals(
                    getUserCacheKeyFromRequestPairSource(webSubjectContext)
            );
        }
        if(Objects.isNull(principalCollection)){
            forgetIdentity(subjectContext);
        }
        return principalCollection;
    }

    protected Object getUserCacheKeyFromRequestPairSource(RequestPairSource requestPairSource){
        HttpServletRequest httpRequest = WebUtils.getHttpRequest(requestPairSource);
        if(Objects.nonNull(httpRequest)){
            String header = httpRequest.getHeader(tokenRequestHeaderName);
            if(StringUtils.hasText(header)){
                return header;
            }
            header = httpRequest.getParameter(tokenRequestHeaderName);
            return header;
        }
        return null;
    }

    /**
     * 获取身份证明
     * @param userCacheKey
     * @return
     */
    protected PrincipalCollection getPrincipals(Object userCacheKey){
        if(Objects.isNull(userCacheKey)){
            return null;
        }
        UserInfo o = (UserInfo) cacheManager.getCache(cacheName).get(userCacheKey);
        if(o == null){
            return null;
        }
        UserDetails userDetails = PrincipalUtils.buildUserDetails(o.getUserId(),
                o.getType());
        if(Objects.isNull(userDetails)){
            return null;
        }
        return PrincipalUtils.createPrincipalCollection(userDetails, o.getRealmName());
    }




    @Override
    public void forgetIdentity(SubjectContext subjectContext) {
        Serializable sessionId = subjectContext.getSessionId();
        if (Objects.nonNull(sessionId)) {
            removeFromCache(sessionId.toString());
        }
        if (subjectContext instanceof WebSubjectContext) {
            forgetIdentityFromRequestPairSource((RequestPairSource) subjectContext);
        }
    }

    protected void forgetIdentityFromRequestPairSource(RequestPairSource requestPairSource){
        if(Objects.nonNull(requestPairSource)){
            HttpServletRequest request = WebUtils.getHttpRequest(requestPairSource);
            if(Objects.nonNull(request)){
                String header = request.getHeader(tokenRequestHeaderName);
                if (StringUtils.hasText(header)) {
                    removeFromCache(header);
                }
                header = request.getParameter(tokenRequestHeaderName);
                if(StringUtils.hasText(header)){
                    removeFromCache(header);
                }
            }

        }
    }

    protected void removeFromCache(Object cacheKey){
        if(Objects.nonNull(cacheKey)){
            cacheManager.getCache(cacheName).remove(cacheKey);
        }
    }

    @Override
    public void onSuccessfulLogin(Subject subject, AuthenticationToken token, AuthenticationInfo info) {
        if (WebUtils.isHttp(subject)) {

            forgetIdentityFromRequestPairSource((RequestPairSource)subject);
        }
        boolean rememberMe = isRememberMe(token);
        logger.debug("登录成功: accessToken:{}, \r\n AuthenticationInfo:{}, \r\n 是否记住我：{}", subject.getSession().getId(), info,rememberMe);
        //now save the new identity:
        if (true) { // 强制存储
            Serializable id = subject.getSession().getId();
            PrincipalCollection principals = info.getPrincipals();
            Set<String> realmNames = principals.getRealmNames();
            UserDetails userDetails = (UserDetails) principals.getPrimaryPrincipal();
            UserInfo userInfo = new UserInfo();
            userInfo.setType(userDetails.getType());
            userInfo.setUserId(userDetails.getUserId());
            userInfo.setRealmName(realmNames.iterator().next());
            cacheManager.getCache(cacheName).put(id, userInfo);
        }

    }

    protected boolean isRememberMe(AuthenticationToken token) {
        return token != null && (token instanceof RememberMeAuthenticationToken) &&
                ((RememberMeAuthenticationToken) token).isRememberMe();
    }

    @Override
    public void onFailedLogin(Subject subject, AuthenticationToken token, AuthenticationException ae) {
        Session session = subject.getSession(false);
        if(Objects.nonNull(session)){
            removeFromCache(session.getId());
        }
        if(subject instanceof RequestPairSource){
            forgetIdentity((SubjectContext) subject);
        }

    }

    @Override
    public void onLogout(Subject subject) {
        if (subject instanceof WebSubject) {
            forgetIdentityFromRequestPairSource((WebSubject) subject);
        }
        Session session = subject.getSession(false);
        if (session != null) {
            Serializable id = session.getId();
            removeFromCache(id);
        }

    }


    @Override
    public void setCacheManager(CacheManager cacheManager) {
        this.cacheManager = cacheManager;
    }


    public CacheManager getCacheManager(){
        return this.cacheManager;
    }
}
